﻿//--------------------------------------------------------------------------------------
// File: SimpleSample11.cpp
//
// Starting point for new Direct3D 11 Win32 desktop samples.  For a more minimal starting
// point, use the EmptyProject11 sample instead.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#include "DXUT.h"
#include "DXUTgui.h"
#include "DXUTmisc.h"
#include "DXUTCamera.h"
#include "DXUTSettingsDlg.h"
#include "SDKmisc.h"
#include "SDKmesh.h"
#include "resource.h"
#include <fstream>
#include <codecvt>
#include <locale>
#include <array>

#pragma warning( disable : 4100 )

#include <json/json.h>
#include <Shlwapi.h>
#include <tlhelp32.h>
#include <Psapi.h>

#ifdef GFESDKDEMO_BUILD_C
#include "GfeSDKWrapper.h"
#else
#include "GfeSDKWrapper.hpp"
using namespace GfeSDK;
#endif

#include <gfesdk/bindings/cpp/highlights/highlights_types_cpp.h>

#include "Splash.h"

using namespace DirectX;

void TRACE(const char* format, ...)
{
    char buffer[512];

    va_list argptr;
    va_start(argptr, format);
    vsprintf_s(buffer, sizeof(buffer) / sizeof(buffer[0]), format, argptr);
    va_end(argptr);

    OutputDebugStringA(buffer);
}

//--------------------------------------------------------------------------------------
// Global variables
//--------------------------------------------------------------------------------------
CModelViewerCamera          g_Camera;               // A model viewing camera
CDXUTDialogResourceManager  g_DialogResourceManager; // manager for shared resources of dialogs
CDXUTTextHelper*            g_pTxtHelper = nullptr;
CDXUTDialog                 g_HUD;                  // dialog for standard controls
CDXUTDialog                 g_SampleUI;             // dialog for sample specific controls
XMMATRIX                    g_mCenterMesh;

CDXUTSDKMesh                g_Mesh11;

                                                    // Direct3D 11 resources
ID3D11VertexShader*         g_pVertexShader11 = nullptr;
ID3D11PixelShader*          g_pPixelShader11 = nullptr;
ID3D11InputLayout*          g_pLayout11 = nullptr;
ID3D11SamplerState*         g_pSamLinear = nullptr;

//--------------------------------------------------------------------------------------
// Constant buffers
//--------------------------------------------------------------------------------------
#pragma pack(push,1)
struct CB_VS_PER_OBJECT
{
    XMFLOAT4X4  m_mWorldViewProjection;
    XMFLOAT4X4  m_mWorld;
    XMFLOAT4    m_MaterialAmbientColor;
    XMFLOAT4    m_MaterialDiffuseColor;
};

struct CB_VS_PER_FRAME
{
    XMFLOAT3    m_vLightDir;
    float       m_fTime;
    XMFLOAT4    m_LightDiffuse;
};
#pragma pack(pop)

ID3D11Buffer*                       g_pcbVSPerObject11 = nullptr;
ID3D11Buffer*                       g_pcbVSPerFrame11 = nullptr;

enum class AppState
{
    Init,
    LoadResources,
    Main
};

// Highlights
struct HighlightsDataHolder
{
    std::string id;
    bool isScreenshot;
    int startDelta;
    int endDelta;
    std::vector<NVGSDK_LocalizedPair> namePairs;
    std::map<std::string, std::string> namePairsData;
};

struct HighlightsData
{
    std::string gameName;
    std::string defaultLocale;
    std::vector<HighlightsDataHolder> highlightsData;
    std::vector<NVGSDK_Highlight> highlights;
};

GfeSdkWrapper g_highlights;
std::string g_targetPath;
int g_targetPid = 0;
NVGSDK_HighlightSignificance g_sigFilter = NVGSDK_HIGHLIGHT_SIGNIFICANCE_NONE;
NVGSDK_HighlightType g_tagFilter = NVGSDK_HIGHLIGHT_TYPE_NONE;
std::wstring_convert<std::codecvt_utf8<wchar_t>> g_converter;
HighlightsData g_highlightsConfig;
char const* GROUP1_ID = "GROUP1";
char const* GROUP2_ID = "GROUP2";


class SettingsManager
{
public:

    enum class BackgroundColor
    {
        Default,
        Red,
        Blue,
    };

    SettingsManager(const std::wstring& configPath)
        : m_color(BackgroundColor::Default),
        m_configPath(configPath)
    {
        LoadConfig();
    }

    std::array<float, 4> GetBackgroundColorArray() const
    {
        switch (m_color)
        {
        case BackgroundColor::Red:
            return { 0.25f, 0.10f, 0.10f, 0.55f };
        case BackgroundColor::Blue:
            return { 0.10f, 0.10f, 0.25f, 0.55f };
        case BackgroundColor::Default:
        default:
            return { 0.0f, 0.25f, 0.25f, 0.55f };
        }
    }

    std::string GetBackgroundColorString() const
    {
        switch (m_color)
        {
        case BackgroundColor::Red:
            return "Red";
        case BackgroundColor::Blue:
            return "Blue";
        case BackgroundColor::Default:
        default:
            return "Default";
        }
    }

    void ToggleBackgroundColor()
    {
        switch (m_color)
        {
        case BackgroundColor::Default:
            m_color = BackgroundColor::Red;
            break;
        case BackgroundColor::Red:
            m_color = BackgroundColor::Blue;
            break;
        case BackgroundColor::Blue:
            m_color = BackgroundColor::Default;
            break;
        default:
            m_color = BackgroundColor::Default;
        }

        SaveConfig();
    }

    void SaveConfig()
    {
        Json::Value root;
        root["Background"] = GetBackgroundColorString();

        std::ofstream f(m_configPath);
        if (!f.good())
        {
            TRACE("Cannot open config file for saving!");
            return;
        }

        f << root;
    }

    void LoadConfig()
    {
        std::ifstream f(m_configPath);
        if (!f.good())
        {
            return;
        }

        Json::Value root;

        std::string errs;
        Json::CharReaderBuilder builder;
        if (!Json::parseFromStream(builder, f, &root, &errs))
        {
            TRACE("FAILED to read json file\n");
            return;
        }

        m_color = BackgroundColorFromString(root["Background"].asString());
    }

private:

    static BackgroundColor BackgroundColorFromString(const std::string& c)
    {
        if (c == "Red") return BackgroundColor::Red;
        if (c == "Blue") return BackgroundColor::Blue;
        if (c == "Default") return BackgroundColor::Default;

        return BackgroundColor::Default;
    }

    const std::wstring m_configPath;
    BackgroundColor m_color;
};

std::unique_ptr<SettingsManager> g_settingsManager;

AppState g_state = AppState::Init;

//--------------------------------------------------------------------------------------
// Forward declarations
//--------------------------------------------------------------------------------------
LRESULT CALLBACK MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool* pbNoFurtherProcessing,
    void* pUserContext);
void CALLBACK OnKeyboard(UINT nChar, bool bKeyDown, bool bAltDown, void* pUserContext);
void CALLBACK OnGUIEvent(UINT nEvent, int nControlID, CDXUTControl* pControl, void* pUserContext);
void CALLBACK OnFrameMove(double fTime, float fElapsedTime, void* pUserContext);
bool CALLBACK ModifyDeviceSettings(DXUTDeviceSettings* pDeviceSettings, void* pUserContext);

bool CALLBACK IsD3D11DeviceAcceptable(const CD3D11EnumAdapterInfo *AdapterInfo, UINT Output,
    const CD3D11EnumDeviceInfo *DeviceInfo,
    DXGI_FORMAT BackBufferFormat, bool bWindowed, void* pUserContext);
HRESULT CALLBACK OnD3D11CreateDevice(ID3D11Device* pd3dDevice, const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc,
    void* pUserContext);
HRESULT CALLBACK OnD3D11ResizedSwapChain(ID3D11Device* pd3dDevice, IDXGISwapChain* pSwapChain,
    const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext);
void CALLBACK OnD3D11ReleasingSwapChain(void* pUserContext);
void CALLBACK OnD3D11DestroyDevice(void* pUserContext);
void CALLBACK OnD3D11FrameRender(ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, double fTime,
    float fElapsedTime, void* pUserContext);
void CALLBACK OnQuit(void* pUserContext);

void InitApp();
void RenderText();
void RenderLoadingText();
static std::string WideToUtf8(std::wstring const& wideStr);

//--------------------------------------------------------------------------------------
// Entry point to the program. Initializes everything and goes into a message processing
// loop. Idle time is used to render the scene.
//--------------------------------------------------------------------------------------
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    std::wstring exeName;
    bool link = false;

    LPWSTR *szArgList;
    int argCount;

    szArgList = CommandLineToArgvW(GetCommandLine(), &argCount);
    if (!szArgList)
    {
        TRACE("FAILED to read command line\n");
        return -1;
    }

    exeName = PathFindFileName(szArgList[0]);
    for (int i = 0; i < argCount; i++)
    {
        if (std::wstring(szArgList[i]) == L"-link" || std::wstring(szArgList[i]) == L"/link")
        {
            link = true;
        }
    }

    LocalFree(szArgList);

    if (link)
    {
        // Try to find another instance of the app already running, to link to it.
        DWORD myPid = GetCurrentProcessId();
        PROCESSENTRY32 entry;
        entry.dwSize = sizeof(PROCESSENTRY32);

        HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

        if (Process32FirstW(snapshot, &entry) == TRUE)
        {
            while (Process32NextW(snapshot, &entry) == TRUE)
            {
                if (exeName == entry.szExeFile && entry.th32ProcessID != myPid)
                {
                    // Found the target to link to
                    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
                    if (hProcess == NULL)
                    {
                        TRACE("Could not open target process");
                        continue;
                    }

                    wchar_t szTargetFileName[MAX_PATH];
                    if (GetModuleFileNameExW(hProcess, NULL, szTargetFileName, MAX_PATH) > 0)
                    {
                        TRACE("Found process to link to!");
                        g_targetPath = WideToUtf8(szTargetFileName);
                        g_targetPid = entry.th32ProcessID;
                    }
                    else
                    {
                        TRACE("Unable to get target path from process handle");
                    }

                    CloseHandle(hProcess);
                }
            }
        }

        CloseHandle(snapshot);

        if (g_targetPath.empty() || g_targetPid == 0)
        {
            MessageBox(NULL, L"-link passed but cannot find target process", L"Error", MB_OK);
            return -1;
        }
    }

    // Enable run-time memory check for debug builds.
#ifdef _DEBUG
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

    if (ShowSplash(hInstance, IDT_TIMER1, IDB_BITMAP1) == 1)
    {
        HideSplash();
        return 0;
    }

    // DXUT will create and use the best device
    // that is available on the system depending on which D3D callbacks are set below

    // Set DXUT callbacks 
    DXUTSetCallbackMsgProc(MsgProc);
    DXUTSetCallbackKeyboard(OnKeyboard);
    DXUTSetCallbackFrameMove(OnFrameMove);
    DXUTSetCallbackDeviceChanging(ModifyDeviceSettings);

    DXUTSetCallbackD3D11DeviceAcceptable(IsD3D11DeviceAcceptable);
    DXUTSetCallbackD3D11DeviceCreated(OnD3D11CreateDevice);
    DXUTSetCallbackD3D11SwapChainResized(OnD3D11ResizedSwapChain);
    DXUTSetCallbackD3D11SwapChainReleasing(OnD3D11ReleasingSwapChain);
    DXUTSetCallbackD3D11DeviceDestroyed(OnD3D11DestroyDevice);
    DXUTSetCallbackD3D11FrameRender(OnD3D11FrameRender);

    DXUTSetCallbackQuit(OnQuit);

    InitApp();
    DXUTInit(true, true, nullptr); // Parse the command line, show msgboxes on error, no extra command line params
    DXUTSetCursorSettings(true, true);
    DXUTCreateWindow(L"SimpleSample11");

    // Only require 10-level hardware, change to D3D_FEATURE_LEVEL_11_0 to require 11-class hardware
    // Switch to D3D_FEATURE_LEVEL_9_x for 10level9 hardware
    DXUTCreateDevice(D3D_FEATURE_LEVEL_10_0, true, 1280, 1024);

    HideSplash();

    DXUTMainLoop(); // Enter into the DXUT render loop

    return DXUTGetExitCode();
}

void ToggleSignificanceBit(NVGSDK_HighlightSignificance bit)
{
    g_sigFilter = static_cast<NVGSDK_HighlightSignificance>(g_sigFilter ^ bit);
}

void ToggleTagBit(NVGSDK_HighlightType bit)
{
    g_tagFilter = static_cast<NVGSDK_HighlightType>(g_tagFilter ^ bit);
}

void HandleOpsNotification()
{
    g_settingsManager->LoadConfig();
}

std::wstring HK(wchar_t const* str, uint16_t hk_)
{
    uint8_t hk = (0xff & hk_);
    std::wstring ret = std::wstring(str) + L" [";

    if (hk >= VK_F1 && hk <= VK_F12)
    {
        ret += L"F" + std::to_wstring(hk - 0x6f);
    }
    else if (hk >= VK_NUMPAD0 && hk <= VK_NUMPAD9)
    {
        ret += L"N" + std::to_wstring(hk - 0x5f);
    }
    else
    {
        ret += (wchar_t)hk;
    }
    return ret + L"]";
}

//--------------------------------------------------------------------------------------
// Initialize the app
//--------------------------------------------------------------------------------------
void InitApp()
{
    g_HUD.Init(&g_DialogResourceManager);
    g_SampleUI.Init(&g_DialogResourceManager);

    g_HUD.SetCallback(OnGUIEvent);

    InitGfeSdkWrapper(&g_highlights);

    // Find the path to the executable
    wchar_t path[MAX_PATH] = { 0 };
    GetModuleFileNameW(NULL, path, MAX_PATH);
    PathRemoveFileSpecW(path);
    const std::wstring pathStr(path);

    g_settingsManager.reset(new SettingsManager(pathStr + L"/settings.json"));

    // Load highlights config
    Json::Value jsonHighlightsConfig;
    {
        std::ifstream inFile(pathStr + L"/highlights_config.json");
        if (!inFile.good())
        {
            inFile = std::ifstream(pathStr + L"/../highlights_config.json");
            if (!inFile.good())
            {
                inFile = std::ifstream(pathStr + L"/../../highlights_config.json");
            }
        }

        std::string errs;
        Json::CharReaderBuilder builder;
        if (!Json::parseFromStream(builder, inFile, &jsonHighlightsConfig, &errs))
        {
            TRACE("FAILED to read json file\n");
        }
    }

    // Store the json data in the highlights data structure
    g_highlightsConfig.gameName = jsonHighlightsConfig["gameName"].asString();
    g_highlightsConfig.defaultLocale = jsonHighlightsConfig["defaultLocale"].asString();
    g_highlightsConfig.highlightsData.resize(jsonHighlightsConfig["highlights"].size());
    g_highlightsConfig.highlights.resize(jsonHighlightsConfig["highlights"].size());

    for (unsigned int i = 0; i < jsonHighlightsConfig["highlights"].size(); ++i)
    {
        Json::Value const& def = jsonHighlightsConfig["highlights"].get(i, Json::Value());
        g_highlightsConfig.highlightsData[i].id = def["id"].asString();
        g_highlightsConfig.highlightsData[i].isScreenshot = !def.isMember("startDelta");
        g_highlightsConfig.highlightsData[i].startDelta = g_highlightsConfig.highlightsData[i].isScreenshot ? 0 : def["startDelta"].asInt();
        g_highlightsConfig.highlightsData[i].endDelta = g_highlightsConfig.highlightsData[i].isScreenshot ? 0 : def["endDelta"].asInt();
        for (auto it = def["name"].begin(); it != def["name"].end(); ++it)
        {
            g_highlightsConfig.highlightsData[i].namePairsData[it.key().asString()] = it->asString();
        }
        for (auto it = g_highlightsConfig.highlightsData[i].namePairsData.begin(); it != g_highlightsConfig.highlightsData[i].namePairsData.end(); ++it)
        {
            g_highlightsConfig.highlightsData[i].namePairs.push_back({ &it->first[0],&it->second[0] });
        }

        g_highlightsConfig.highlights[i].id = g_highlightsConfig.highlightsData[i].id.c_str();
        g_highlightsConfig.highlights[i].userInterest = def["userInterest"].asBool();
        g_highlightsConfig.highlights[i].significance = static_cast<NVGSDK_HighlightSignificance>(def["significance"].asInt());
        g_highlightsConfig.highlights[i].highlightTags = static_cast<NVGSDK_HighlightType>(def["highlightTags"].asInt());
        g_highlightsConfig.highlights[i].nameTable = g_highlightsConfig.highlightsData[i].namePairs.empty() ? nullptr : &g_highlightsConfig.highlightsData[i].namePairs[0];
        g_highlightsConfig.highlights[i].nameTableSize = g_highlightsConfig.highlightsData[i].namePairs.size();
    }

    int iY = 10;
    int const PAD = 26;
    int const WIDTH = 240;
    int const X = -80;

    uint16_t hk = 0x30;
    g_HUD.AddButtonCallback([]() {DXUTToggleFullScreen(); }, HK(L"Toggle full screen", hk).c_str(), X, iY, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback([]() { g_settingsManager->ToggleBackgroundColor(); }, HK(L"Toggle background color", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback([]() {g_highlights.OnRequestLanguage(); }, HK(L"Get Overlay Language", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback([]() {g_highlights.OnRequestUserSettings(); }, HK(L"Get User Settings", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);

    iY += 10;

    int iY_groups = iY;
    int const GROUP1_X = X - WIDTH - 10;
    int const GROUP2_X = X;

    hk = 0x40;
    g_HUD.AddButtonCallback([]() {g_highlights.OnOpenGroup(GROUP1_ID); }, HK(L"Open Group 1", hk).c_str(), GROUP1_X, iY += PAD, WIDTH, 23, ++hk);
    for (size_t i = 0; i < g_highlightsConfig.highlightsData.size(); ++i)
    {
        HighlightsDataHolder const& holder = g_highlightsConfig.highlightsData[i];
        if (!holder.isScreenshot)
        {
            std::wstring text = std::wstring(L"VIDEO: ") + g_converter.from_bytes(holder.id);
            g_HUD.AddButtonCallback([holder]() { g_highlights.OnSaveVideo(holder.id.c_str(), GROUP1_ID, holder.startDelta, holder.endDelta); },
                HK(text.c_str(), hk).c_str(), GROUP1_X, iY += PAD, WIDTH, 23, ++hk);
        }
        else
        {
            std::wstring text = std::wstring(L"SCREENSHOT: ") + g_converter.from_bytes(holder.id);
            g_HUD.AddButtonCallback([holder]() { g_highlights.OnSaveScreenshot(holder.id.c_str(), GROUP1_ID); }, HK(text.c_str(), hk).c_str(), GROUP1_X, iY += PAD, WIDTH, 23, ++hk);
        }
    }

    g_HUD.AddButtonCallback([]() {g_highlights.OnOpenSummary(&std::vector<char const*>({ GROUP1_ID })[0], 1, g_sigFilter, g_tagFilter); },
        HK(L"Open Group1 Summary", hk).c_str(), GROUP1_X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback([]() {g_highlights.OnGetNumHighlights(GROUP1_ID, g_sigFilter, g_tagFilter); }, HK(L"GetNumHighlights in Group1", hk).c_str(), GROUP1_X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback([]() {g_highlights.OnCloseGroup(GROUP1_ID, false); }, HK(L"Close Group 1", hk).c_str(), GROUP1_X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback([]() {g_highlights.OnCloseGroup(GROUP1_ID, true); }, HK(L"Destroy Group 1", hk).c_str(), GROUP1_X, iY += PAD, WIDTH, 23, ++hk);

    hk = 0x4d;
    iY = iY_groups;
    g_HUD.AddButtonCallback([]() {g_highlights.OnOpenGroup(GROUP2_ID); }, HK(L"Open Group 2", hk).c_str(), GROUP2_X, iY += PAD, WIDTH, 23, ++hk);
    for (size_t i = 0; i < g_highlightsConfig.highlightsData.size(); ++i)
    {
        HighlightsDataHolder const& holder = g_highlightsConfig.highlightsData[i];
        if (!holder.isScreenshot)
        {
            std::wstring text = std::wstring(L"VIDEO: ") + g_converter.from_bytes(holder.id);
            g_HUD.AddButtonCallback([holder]() { g_highlights.OnSaveVideo(holder.id.c_str(), GROUP2_ID, holder.startDelta, holder.endDelta); },
                HK(text.c_str(), hk).c_str(), GROUP2_X, iY += PAD, WIDTH, 23, ++hk);
        }
        else
        {
            std::wstring text = std::wstring(L"SCREENSHOT: ") + g_converter.from_bytes(holder.id);
            g_HUD.AddButtonCallback([holder]() { g_highlights.OnSaveScreenshot(holder.id.c_str(), GROUP2_ID); },
                HK(text.c_str(), hk).c_str(), GROUP2_X, iY += PAD, WIDTH, 23, ++hk);
        }
    }
    g_HUD.AddButtonCallback([]() {g_highlights.OnOpenSummary(&std::vector<char const*>({ GROUP2_ID })[0], 1, g_sigFilter, g_tagFilter); },
        HK(L"Open Group2 Summary", hk).c_str(), GROUP2_X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback([]() {g_highlights.OnGetNumHighlights(GROUP2_ID, g_sigFilter, g_tagFilter); }, HK(L"GetNumHighlights in Group2", hk).c_str(), GROUP2_X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback([]() {g_highlights.OnCloseGroup(GROUP2_ID, false); }, HK(L"Close Group 2", hk).c_str(), GROUP2_X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback([]() {g_highlights.OnCloseGroup(GROUP2_ID, true); }, HK(L"Destroy Group 2", hk).c_str(), GROUP2_X, iY += PAD, WIDTH, 23, ++hk);

    hk = 0x59;
    iY += 10;
    g_HUD.AddButtonCallback([]() {g_highlights.OnOpenSummary(&std::vector<char const*>({ GROUP1_ID, GROUP2_ID })[0], 2, g_sigFilter, g_tagFilter); },
        HK(L"Open GroupSummary 1 + 2", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);

    hk = 0x6f;
    iY += 20;
    g_HUD.AddButtonCallback([]() {g_sigFilter = NVGSDK_HIGHLIGHT_SIGNIFICANCE_NONE; }, HK(L"Clear Significance Filter", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleSignificanceBit, NVGSDK_HIGHLIGHT_SIGNIFICANCE_EXTREMELY_BAD), HK(L"Toggle ExtremelyBad", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleSignificanceBit, NVGSDK_HIGHLIGHT_SIGNIFICANCE_VERY_BAD), HK(L"Toggle VeryBad", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleSignificanceBit, NVGSDK_HIGHLIGHT_SIGNIFICANCE_BAD), HK(L"Toggle Bad", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleSignificanceBit, NVGSDK_HIGHLIGHT_SIGNIFICANCE_NEUTRAL), HK(L"Toggle Neutral", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleSignificanceBit, NVGSDK_HIGHLIGHT_SIGNIFICANCE_GOOD), HK(L"Toggle Good", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleSignificanceBit, NVGSDK_HIGHLIGHT_SIGNIFICANCE_VERY_GOOD), HK(L"Toggle VeryGood", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleSignificanceBit, NVGSDK_HIGHLIGHT_SIGNIFICANCE_EXTREMELY_GOOD), HK(L"Toggle ExtremelyGood", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);

    hk = 0x5f;
    iY += 10;
    g_HUD.AddButtonCallback([]() {g_tagFilter = NVGSDK_HIGHLIGHT_TYPE_NONE; }, HK(L"Clear Tag Filter", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleTagBit, NVGSDK_HIGHLIGHT_TYPE_MILESTONE), HK(L"Toggle Milestone", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleTagBit, NVGSDK_HIGHLIGHT_TYPE_ACHIEVEMENT), HK(L"Toggle Achievement", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleTagBit, NVGSDK_HIGHLIGHT_TYPE_INCIDENT), HK(L"Toggle Incident", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleTagBit, NVGSDK_HIGHLIGHT_TYPE_STATE_CHANGE), HK(L"Toggle StateChange", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);
    g_HUD.AddButtonCallback(std::bind(ToggleTagBit, NVGSDK_HIGHLIGHT_TYPE_UNANNOUNCED), HK(L"Toggle Unannounced", hk).c_str(), X, iY += PAD, WIDTH, 23, ++hk);

    g_SampleUI.SetCallback(OnGUIEvent); iY = 10;
}


//--------------------------------------------------------------------------------------
// Render the help and statistics text.
//--------------------------------------------------------------------------------------
void RenderText()
{
    UINT nBackBufferHeight = DXUTGetDXGIBackBufferSurfaceDesc()->Height;

    g_pTxtHelper->Begin();
    g_pTxtHelper->SetInsertionPos(2, 50);
    g_pTxtHelper->SetForegroundColor(Colors::White);
    g_pTxtHelper->DrawTextLine(DXUTGetFrameStats(DXUTIsVsyncEnabled()));
    g_pTxtHelper->DrawTextLine(DXUTGetDeviceStats());

    // Settings
    g_pTxtHelper->DrawFormattedTextLine(L"Background color: %S", g_settingsManager->GetBackgroundColorString().c_str());

    // Highlights
    g_pTxtHelper->SetForegroundColor(Colors::White);
    g_pTxtHelper->SetInsertionPos(2, 5);
    g_pTxtHelper->DrawFormattedTextLine(L"Highlights Permission: %s", g_highlights.GetCurrentPermissionStr());
    g_pTxtHelper->DrawFormattedTextLine(L"Last Overlay Event: %s", g_highlights.GetLastOverlayEvent());
    g_pTxtHelper->DrawFormattedTextLine(L"Last Result: %s", g_highlights.GetLastResult());

    g_pTxtHelper->SetInsertionPos(500, 5);
    g_pTxtHelper->DrawFormattedTextLine(L"Last Query: %s", g_highlights.GetLastQueryResult());

    g_pTxtHelper->SetInsertionPos(700, 5);
    g_pTxtHelper->DrawFormattedTextLine(L"Significance Filter: 0x%08x", g_sigFilter);
    g_pTxtHelper->DrawFormattedTextLine(L"Tag Filter: 0x%08x", g_tagFilter);

    g_pTxtHelper->End();
}

//--------------------------------------------------------------------------------------
// Render the loading text
//--------------------------------------------------------------------------------------
void RenderLoadingText()
{
    UINT nBackBufferHeight = DXUTGetDXGIBackBufferSurfaceDesc()->Height;

    g_pTxtHelper->Begin();

    g_pTxtHelper->SetInsertionPos(nBackBufferHeight / 2, 300);
    g_pTxtHelper->SetForegroundColor(Colors::White);
    g_pTxtHelper->DrawTextLine(L"LOADING...");

    g_pTxtHelper->End();
}

std::string WideToUtf8(std::wstring const& wideStr)
{
    int length = WideCharToMultiByte(CP_UTF8, 0, wideStr.c_str(), -1, NULL, 0, NULL, NULL);
    if (length <= 0)
    {
        throw std::runtime_error("WideCharToMultiByte failed");
    }

    std::unique_ptr<char[]> outputData(new char[length]);

    length = WideCharToMultiByte(CP_UTF8, 0, wideStr.c_str(), -1, &outputData.get()[0], length, NULL, NULL);
    if (length <= 0)
    {
        throw std::runtime_error("WideCharToMultiByte failed");
    }

    return std::string(&outputData.get()[0]);
}

//--------------------------------------------------------------------------------------
// Reject any D3D11 devices that aren't acceptable by returning false
//--------------------------------------------------------------------------------------
bool CALLBACK IsD3D11DeviceAcceptable(const CD3D11EnumAdapterInfo *AdapterInfo, UINT Output,
    const CD3D11EnumDeviceInfo *DeviceInfo,
    DXGI_FORMAT BackBufferFormat, bool bWindowed, void* pUserContext)
{
    return true;
}


//--------------------------------------------------------------------------------------
// Create any D3D11 resources that aren't dependant on the back buffer
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D11CreateDevice(ID3D11Device* pd3dDevice, const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc,
    void* pUserContext)
{
    HRESULT hr;

    auto pd3dImmediateContext = DXUTGetD3D11DeviceContext();
    V_RETURN(g_DialogResourceManager.OnD3D11CreateDevice(pd3dDevice, pd3dImmediateContext));
    g_pTxtHelper = new CDXUTTextHelper(pd3dDevice, pd3dImmediateContext, &g_DialogResourceManager, 15);

    XMVECTORF32 vCenter = { 0.25767413f, -28.503521f, 111.00689f };
    FLOAT fObjectRadius = 378.15607f;

    g_mCenterMesh = XMMatrixTranslation(-vCenter.f[0], -vCenter.f[1], -vCenter.f[2]);
    XMMATRIX m = XMMatrixRotationY(XM_PI);
    g_mCenterMesh *= m;
    m = XMMatrixRotationX(XM_PI / 2.0f);
    g_mCenterMesh *= m;

    // Read the HLSL file
    // You should use the lowest possible shader profile for your shader to enable various feature levels. These
    // shaders are simple enough to work well within the lowest possible profile, and will run on all feature levels

    DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef _DEBUG
    // Disable optimizations to further improve shader debugging
    dwShaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

    ID3DBlob* pVertexShaderBuffer = nullptr;
    V_RETURN(DXUTCompileFromFile(L"SimpleSample.hlsl", nullptr, "RenderSceneVS", "vs_4_0_level_9_1", dwShaderFlags, 0,
        &pVertexShaderBuffer));

    ID3DBlob* pPixelShaderBuffer = nullptr;
    V_RETURN(DXUTCompileFromFile(L"SimpleSample.hlsl", nullptr, "RenderScenePS", "ps_4_0_level_9_1", dwShaderFlags, 0,
        &pPixelShaderBuffer));

    // Create the shaders
    V_RETURN(pd3dDevice->CreateVertexShader(pVertexShaderBuffer->GetBufferPointer(),
        pVertexShaderBuffer->GetBufferSize(), nullptr, &g_pVertexShader11));
    DXUT_SetDebugName(g_pVertexShader11, "RenderSceneVS");

    V_RETURN(pd3dDevice->CreatePixelShader(pPixelShaderBuffer->GetBufferPointer(),
        pPixelShaderBuffer->GetBufferSize(), nullptr, &g_pPixelShader11));
    DXUT_SetDebugName(g_pPixelShader11, "RenderScenePS");

    // Create a layout for the object data
    const D3D11_INPUT_ELEMENT_DESC layout[] =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,  0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };

    V_RETURN(pd3dDevice->CreateInputLayout(layout, ARRAYSIZE(layout), pVertexShaderBuffer->GetBufferPointer(),
        pVertexShaderBuffer->GetBufferSize(), &g_pLayout11));
    DXUT_SetDebugName(g_pLayout11, "Primary");

    // Load the mesh
    V_RETURN(g_Mesh11.Create(pd3dDevice, L"tiny\\tiny.sdkmesh"));

    // No longer need the shader blobs
    SAFE_RELEASE(pVertexShaderBuffer);
    SAFE_RELEASE(pPixelShaderBuffer);

    // Create state objects
    D3D11_SAMPLER_DESC samDesc;
    ZeroMemory(&samDesc, sizeof(samDesc));
    samDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    samDesc.AddressU = samDesc.AddressV = samDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    samDesc.MaxAnisotropy = 1;
    samDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
    samDesc.MaxLOD = D3D11_FLOAT32_MAX;
    V_RETURN(pd3dDevice->CreateSamplerState(&samDesc, &g_pSamLinear));
    DXUT_SetDebugName(g_pSamLinear, "Linear");

    // Create constant buffers
    D3D11_BUFFER_DESC cbDesc;
    ZeroMemory(&cbDesc, sizeof(cbDesc));
    cbDesc.Usage = D3D11_USAGE_DYNAMIC;
    cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

    cbDesc.ByteWidth = sizeof(CB_VS_PER_OBJECT);
    V_RETURN(pd3dDevice->CreateBuffer(&cbDesc, nullptr, &g_pcbVSPerObject11));
    DXUT_SetDebugName(g_pcbVSPerObject11, "CB_VS_PER_OBJECT");

    cbDesc.ByteWidth = sizeof(CB_VS_PER_FRAME);
    V_RETURN(pd3dDevice->CreateBuffer(&cbDesc, nullptr, &g_pcbVSPerFrame11));
    DXUT_SetDebugName(g_pcbVSPerFrame11, "CB_VS_PER_FRAME");

    // Create other render resources here

    // Setup the camera's view parameters
    static const XMVECTORF32 s_vecEye = { 0.0f, 0.0f, -100.0f };
    static const XMVECTORF32 s_vecAt = { 0.0f, 0.0f, -0.0f };

    g_Camera.SetViewParams(s_vecEye, s_vecAt);
    g_Camera.SetRadius(fObjectRadius * 3.0f, fObjectRadius * 0.5f, fObjectRadius * 10.0f);

    return S_OK;
}


//--------------------------------------------------------------------------------------
// Create any D3D11 resources that depend on the back buffer
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D11ResizedSwapChain(ID3D11Device* pd3dDevice, IDXGISwapChain* pSwapChain,
    const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext)
{
    HRESULT hr;

    V_RETURN(g_DialogResourceManager.OnD3D11ResizedSwapChain(pd3dDevice, pBackBufferSurfaceDesc));

    // Setup the camera's projection parameters
    float fAspectRatio = pBackBufferSurfaceDesc->Width / (FLOAT)pBackBufferSurfaceDesc->Height;
    g_Camera.SetProjParams(XM_PI / 4, fAspectRatio, 2.0f, 10000.0f);
    g_Camera.SetWindow(pBackBufferSurfaceDesc->Width, pBackBufferSurfaceDesc->Height);
    g_Camera.SetButtonMasks(MOUSE_LEFT_BUTTON, MOUSE_WHEEL, MOUSE_MIDDLE_BUTTON);

    g_HUD.SetLocation(pBackBufferSurfaceDesc->Width - 170, 0);
    g_HUD.SetSize(170, 170);
    g_SampleUI.SetLocation(pBackBufferSurfaceDesc->Width - 170, pBackBufferSurfaceDesc->Height - 300);
    g_SampleUI.SetSize(170, 300);

    return S_OK;
}

static DWORD LastFrameTime = 0;
DWORD FPSLimit = 60;

//--------------------------------------------------------------------------------------
// Render the scene using the D3D11 device
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D11FrameRender(ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, double fTime,
    float fElapsedTime, void* pUserContext)
{
    // Limit FPS
    DWORD currentTime = timeGetTime();
    if ((currentTime - LastFrameTime) < (1000 / FPSLimit))
    {
        Sleep(currentTime - LastFrameTime);
    }
    LastFrameTime = currentTime;

    g_highlights.OnTick();

    switch (g_state)
    {
    case AppState::Init:
        g_state = AppState::LoadResources;
        return;
    case AppState::LoadResources:
        RenderLoadingText();
        g_highlights.Init(g_highlightsConfig.gameName.c_str(), g_highlightsConfig.defaultLocale.c_str(), &g_highlightsConfig.highlights[0], g_highlightsConfig.highlights.size(),
            g_targetPath.c_str(), g_targetPid);
        g_state = AppState::Main;
        return;
    case AppState::Main:
    {
        auto pRTV = DXUTGetD3D11RenderTargetView();
        pd3dImmediateContext->ClearRenderTargetView(pRTV, g_settingsManager->GetBackgroundColorArray().data());

        // Clear the depth stencil
        auto pDSV = DXUTGetD3D11DepthStencilView();
        pd3dImmediateContext->ClearDepthStencilView(pDSV, D3D11_CLEAR_DEPTH, 1.0, 0);

        // Slowly rotate world
        XMMATRIX rotMatrix = XMMatrixRotationRollPitchYaw(0.0f, 0.001f, 0.001f);
        g_mCenterMesh = g_mCenterMesh * rotMatrix;

        // Get the projection & view matrix from the camera class
        XMMATRIX mWorld = g_mCenterMesh * g_Camera.GetWorldMatrix();
        XMMATRIX mView = g_Camera.GetViewMatrix();
        XMMATRIX mProj = g_Camera.GetProjMatrix();
        XMMATRIX mWorldViewProjection = mWorld * mView * mProj;

        // Set the constant buffers
        HRESULT hr;
        D3D11_MAPPED_SUBRESOURCE MappedResource;
        V(pd3dImmediateContext->Map(g_pcbVSPerFrame11, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource));
        auto pVSPerFrame = reinterpret_cast<CB_VS_PER_FRAME*>(MappedResource.pData);
        pVSPerFrame->m_vLightDir = XMFLOAT3(0, 0.707f, -0.707f);
        pVSPerFrame->m_fTime = (float)fTime;
        pVSPerFrame->m_LightDiffuse = XMFLOAT4(1.f, 1.f, 1.f, 1.f);
        pd3dImmediateContext->Unmap(g_pcbVSPerFrame11, 0);
        pd3dImmediateContext->VSSetConstantBuffers(1, 1, &g_pcbVSPerFrame11);

        V(pd3dImmediateContext->Map(g_pcbVSPerObject11, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource));
        auto pVSPerObject = reinterpret_cast<CB_VS_PER_OBJECT*>(MappedResource.pData);
        XMStoreFloat4x4(&pVSPerObject->m_mWorldViewProjection, XMMatrixTranspose(mWorldViewProjection));
        XMStoreFloat4x4(&pVSPerObject->m_mWorld, XMMatrixTranspose(mWorld));
        pVSPerObject->m_MaterialAmbientColor = XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f);
        pVSPerObject->m_MaterialDiffuseColor = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f);
        pd3dImmediateContext->Unmap(g_pcbVSPerObject11, 0);
        pd3dImmediateContext->VSSetConstantBuffers(0, 1, &g_pcbVSPerObject11);

        //Get the mesh
        //IA setup
        pd3dImmediateContext->IASetInputLayout(g_pLayout11);
        UINT Strides[1];
        UINT Offsets[1];
        ID3D11Buffer* pVB[1];
        pVB[0] = g_Mesh11.GetVB11(0, 0);
        Strides[0] = (UINT)g_Mesh11.GetVertexStride(0, 0);
        Offsets[0] = 0;
        pd3dImmediateContext->IASetVertexBuffers(0, 1, pVB, Strides, Offsets);
        pd3dImmediateContext->IASetIndexBuffer(g_Mesh11.GetIB11(0), g_Mesh11.GetIBFormat11(0), 0);

        // Set render resources
        pd3dImmediateContext->IASetInputLayout(g_pLayout11);
        pd3dImmediateContext->VSSetShader(g_pVertexShader11, nullptr, 0);
        pd3dImmediateContext->PSSetShader(g_pPixelShader11, nullptr, 0);
        pd3dImmediateContext->PSSetSamplers(0, 1, &g_pSamLinear);

        // Render objects here...
        SDKMESH_SUBSET* pSubset = NULL;
        D3D11_PRIMITIVE_TOPOLOGY PrimType;
        for (UINT subset = 0; subset < g_Mesh11.GetNumSubsets(0); ++subset)
        {
            // Get the subset
            pSubset = g_Mesh11.GetSubset(0, subset);

            PrimType = CDXUTSDKMesh::GetPrimitiveType11((SDKMESH_PRIMITIVE_TYPE)pSubset->PrimitiveType);
            pd3dImmediateContext->IASetPrimitiveTopology(PrimType);

            // TODO: D3D11 - material loading
            ID3D11ShaderResourceView* pDiffuseRV = g_Mesh11.GetMaterial(pSubset->MaterialID)->pDiffuseRV11;
            pd3dImmediateContext->PSSetShaderResources(0, 1, &pDiffuseRV);

            pd3dImmediateContext->DrawIndexed((UINT)pSubset->IndexCount, 0, (UINT)pSubset->VertexStart);
        }

        DXUT_BeginPerfEvent(DXUT_PERFEVENTCOLOR, L"HUD / Stats");
        g_HUD.OnRender(fElapsedTime);
        g_SampleUI.OnRender(fElapsedTime);
        RenderText();
        DXUT_EndPerfEvent();

        static ULONGLONG timefirst = GetTickCount64();
        if (GetTickCount64() - timefirst > 5000)
        {
            OutputDebugString(DXUTGetFrameStats(DXUTIsVsyncEnabled()));
            OutputDebugString(L"\n");
            timefirst = GetTickCount64();
        }
        return;
    }
    }
}

void CALLBACK OnQuit(void* pUserContext)
{
    g_highlights.DeInit();
    PostQuitMessage(0);
}

//--------------------------------------------------------------------------------------
// Release D3D11 resources created in OnD3D11ResizedSwapChain
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D11ReleasingSwapChain(void* pUserContext)
{
    g_DialogResourceManager.OnD3D11ReleasingSwapChain();
}


//--------------------------------------------------------------------------------------
// Release D3D11 resources created in OnD3D11CreateDevice
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D11DestroyDevice(void* pUserContext)
{
    g_DialogResourceManager.OnD3D11DestroyDevice();
    DXUTGetGlobalResourceCache().OnDestroyDevice();
    SAFE_DELETE(g_pTxtHelper);

    g_Mesh11.Destroy();

    SAFE_RELEASE(g_pVertexShader11);
    SAFE_RELEASE(g_pPixelShader11);
    SAFE_RELEASE(g_pLayout11);
    SAFE_RELEASE(g_pSamLinear);

    // Delete additional render resources here...

    SAFE_RELEASE(g_pcbVSPerObject11);
    SAFE_RELEASE(g_pcbVSPerFrame11);
}


//--------------------------------------------------------------------------------------
// Called right before creating a D3D device, allowing the app to modify the device settings as needed
//--------------------------------------------------------------------------------------
bool CALLBACK ModifyDeviceSettings(DXUTDeviceSettings* pDeviceSettings, void* pUserContext)
{
    return true;
}


//--------------------------------------------------------------------------------------
// Handle updates to the scene.  This is called regardless of which D3D API is used
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameMove(double fTime, float fElapsedTime, void* pUserContext)
{
    // Update the camera's position based on user input
    g_Camera.FrameMove(fElapsedTime);
}


//--------------------------------------------------------------------------------------
// Handle messages to the application
//--------------------------------------------------------------------------------------
LRESULT CALLBACK MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool* pbNoFurtherProcessing,
    void* pUserContext)
{
    // Pass messages to dialog resource manager calls so GUI state is updated correctly
    *pbNoFurtherProcessing = g_DialogResourceManager.MsgProc(hWnd, uMsg, wParam, lParam);
    if (*pbNoFurtherProcessing)
        return 0;

    // Give the dialogs a chance to handle the message first
    *pbNoFurtherProcessing = g_HUD.MsgProc(hWnd, uMsg, wParam, lParam);
    if (*pbNoFurtherProcessing)
        return 0;
    *pbNoFurtherProcessing = g_SampleUI.MsgProc(hWnd, uMsg, wParam, lParam);
    if (*pbNoFurtherProcessing)
        return 0;

    // Pass all remaining windows messages to camera so it can respond to user input
    g_Camera.HandleMessages(hWnd, uMsg, wParam, lParam);

    return 0;
}


//--------------------------------------------------------------------------------------
// Handle key presses
//--------------------------------------------------------------------------------------
void CALLBACK OnKeyboard(UINT nChar, bool bKeyDown, bool bAltDown, void* pUserContext)
{
}


//--------------------------------------------------------------------------------------
// Handles the GUI events
//--------------------------------------------------------------------------------------
void CALLBACK OnGUIEvent(UINT nEvent, int nControlID, CDXUTControl* pControl, void* pUserContext)
{
    void* pUserData = pControl->GetUserData();
    if (pUserData)
    {
        std::function<void()>* fn = reinterpret_cast<std::function<void()>*>(pUserData);
        (*fn)();
    }
}
